﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using HDF.PInvoke;
using QuantBox.XApi;

namespace QuantBox
{
    public static class HdfReader
    {
        public static bool SaveData = false;
        public static string DataPath = string.Empty;

        /// <summary>
        /// Struct used to read in variable length string data from an HDF5
        /// file.  It is unsafe, therefore, the layout is used to define the
        /// struct.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private unsafe struct VariableLengthString
        {
            /// <summary>
            /// Pointer to the variable length string
            /// </summary>
            private readonly char* recordedText;

            /// <summary>
            /// Returns a System.String that represents the current System.Object.
            /// </summary>
            /// <returns>The value of the variable length string.</returns>
            public override string ToString()
            {
                // The HDF5 STRING is not a string but in fact a char*
                // Therefore, we need to translate the return into a pointer address
                var ipp = (IntPtr)recordedText;

                // This call is used to transform the pointer into the value of the pointer.
                // NOTE:  this only works with null-terminated strings.
                var s = Marshal.PtrToStringAnsi(ipp);

                //// FREE THE MEMORY TO THE POINTER??
                //// System.Runtime.InteropServices.Marshal.FreeHGlobal(ipp);

                return s ?? throw new InvalidOperationException();
            }
        }

        private static string ReadAttributeString(long groupId, string objectName, string attributeName)
        {
            long attributeId = 0;
            long dataTypeId = 0;
            // ReadFuture the Type attribute
            try {
                attributeId = H5A.open_by_name(groupId, objectName, attributeName);
                dataTypeId = H5A.get_type(attributeId);
                var isVariableLength = H5T.is_variable_str(dataTypeId) > 0;

                if (isVariableLength) {
                    // Variable length string attribute
                    // NOTE: This section only works if the array length is 1
                    var buffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VariableLengthString)));
                    H5A.read(attributeId, dataTypeId, buffer);
                    var value = Marshal.PtrToStructure<VariableLengthString>(buffer);
                    Marshal.FreeHGlobal(buffer);
                    return value.ToString();
                }
                else {
                    // Make length smaller so null termination character is not read
                    var length = H5T.get_size(dataTypeId).ToInt32();

                    // Fixed length string attribute
                    var valueBytes = new byte[length];
                    var pinned = GCHandle.Alloc(valueBytes, GCHandleType.Pinned);
                    H5A.read(attributeId, dataTypeId, pinned.AddrOfPinnedObject());
                    pinned.Free();
                    var value = Encoding.ASCII.GetString(valueBytes);
                    return value;
                }
            }
            finally {
                if (dataTypeId != 0) {
                    H5T.close(dataTypeId);
                }
                if (attributeId != 0) {
                    H5A.close(attributeId);
                }
            }
        }

        #region Hdf Tick Type
        private interface ITickRow
        {
            DateTime GetDateTime();
            double GetPrice();
            long GetVolumn();
            double GetOpenInterest();
            double GetSP1();
            long GetSV1();
            double GetBP1();
            long GetBV1();
            double GetTurnover();
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private unsafe struct S5Tick : ITickRow
        {
            private readonly long Index;
            private fixed byte Instrument[5];
            private readonly double Price;
            private readonly long Volumn;
            private readonly double Turnover;
            private readonly double OpenInt;
            private readonly double SP1;
            private readonly long SV1;
            private readonly double BP1;
            private readonly long BV1;

            public DateTime GetDateTime()
            {
                return Skyline.Utility.Epoch.AddSeconds((double)Index / 1000000000);
            }

            public double GetPrice()
            {
                return Price;
            }

            public long GetVolumn()
            {
                return Volumn;
            }

            public double GetOpenInterest()
            {
                return OpenInt;
            }

            public double GetSP1()
            {
                return SP1;
            }

            public long GetSV1()
            {
                return SV1;
            }

            public double GetBP1()
            {
                return BP1;
            }

            public long GetBV1()
            {
                return BV1;
            }

            public double GetTurnover()
            {
                return Turnover;
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private unsafe struct S6Tick : ITickRow
        {
            private readonly long Index;
            private fixed byte Instrument[6];
            private readonly double Price;
            private readonly long Volumn;
            private readonly double Turnover;
            private readonly double OpenInt;
            private readonly double SP1;
            private readonly long SV1;
            private readonly double BP1;
            private readonly long BV1;

            public DateTime GetDateTime()
            {
                return Skyline.Utility.Epoch.AddSeconds((double)Index / 1000000000);
            }

            public double GetPrice()
            {
                return Price;
            }

            public long GetVolumn()
            {
                return Volumn;
            }

            public double GetOpenInterest()
            {
                return OpenInt;
            }

            public double GetSP1()
            {
                return SP1;
            }

            public long GetSV1()
            {
                return SV1;
            }

            public double GetBP1()
            {
                return BP1;
            }

            public long GetBV1()
            {
                return BV1;
            }

            public double GetTurnover()
            {
                return Turnover;
            }
        }
        #endregion

        public static DepthMarketDataField[] Read(Stream stream, string instrument)
        {
            var data = new byte[stream.Length];
            stream.Read(data, 0, (int)stream.Length);
            var h5File = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
            try {
                File.WriteAllBytes(h5File, data);
                return Read(h5File, instrument);
            }
            finally {
                if (File.Exists(h5File)) {
                    File.Delete(h5File);
                }
            }
        }
        
        public static bool IsTickH5(string h5File) 
        {
            var f = H5F.open(h5File, H5F.ACC_RDONLY);
            var dataset = H5D.open(f, "/data/table");
            if (dataset < 0) {
                H5F.close(f);
                return true;
            }
            return false;
        }

        public static DepthMarketDataField[] Read(string h5File, string instrument)
        {
            var f = H5F.open(h5File, H5F.ACC_RDONLY);
            var dataset = H5D.open(f, "/data/table");
            var datatype = H5D.get_type(dataset);
            var space = H5D.get_space(dataset);
            var size = (int)H5S.get_simple_extent_npoints(space);

            IEnumerable<ITickRow> rows;
            try {
                var instrumentType = ReadAttributeString(dataset, "/data/table", "Instrument_dtype");
                if (instrumentType == "bytes40") {
                    var ticks = new S5Tick[size];
                    var pinned = GCHandle.Alloc(ticks, GCHandleType.Pinned);
                    H5D.read(dataset, datatype, H5S.ALL, H5S.ALL, H5P.DEFAULT, pinned.AddrOfPinnedObject());
                    pinned.Free();
                    rows = ticks.Cast<ITickRow>();
                }
                else {
                    var ticks = new S6Tick[size];
                    var pinned = GCHandle.Alloc(ticks, GCHandleType.Pinned);
                    H5D.read(dataset, datatype, H5S.ALL, H5S.ALL, H5P.DEFAULT, pinned.AddrOfPinnedObject());
                    pinned.Free();
                    rows = ticks.Cast<ITickRow>();
                }
            }
            finally {
                H5D.close(dataset);
                H5F.close(f);
            }
            var list = new List<DepthMarketDataField>(size);
            foreach (var row in rows) {
                var tick = new DepthMarketDataField();
                tick.Asks = new DepthField[1];
                tick.Bids = new DepthField[1];
                tick.Symbol = instrument;
                tick.SetExchangeDateTime(row.GetDateTime());
                tick.LastPrice = row.GetPrice();
                tick.Volume = (int)row.GetVolumn();
                tick.OpenInterest = (int)row.GetOpenInterest();
                tick.Turnover = row.GetTurnover();
                tick.Asks[0].Price = row.GetSP1();
                tick.Asks[0].Size = (int)row.GetSV1();
                tick.Bids[0].Price = row.GetBP1();
                tick.Bids[0].Size = (int)row.GetBV1();
                list.Add(tick);
            }
            return list.ToArray();
        }
    }
}
